local api, MAJ, REV = {}, 1, 1

local function assert(condition, err, ...)
	return (not condition) and error(tostring(err):format(...), 3) or condition, err, ...
end

local ab, abCallFuncs = CreateFrame("BUTTON", "ActionBookTrigger", nil, "SecureActionButtonTemplate"), {}
ab:RegisterForClicks("AnyUp", "AnyDown")
function ab:callCustom(unit, button)
	local t = abCallFuncs[button]
	local ttype = type(t)
	if ttype == "table" then
		t[1](unpack(t,3,t[2]))
	elseif ttype == "function" then
		t()
	end
end

local actionTypeHandler = {}
function actionTypeHandler.attribute(id, cnt, name, value, ...)
	if not (type(name) == "string" and value ~= nil) then
		return false, "Invalid attribute name/value pair (%q,%q)", tostring(name), tostring(value)
	end
	ab:SetAttribute("*" .. name .. "-" .. id, value)
	if cnt == 2 then return true end
	return actionTypeHandler.attribute(id, cnt-2, ...);
end
function actionTypeHandler.func(id, cnt, func, ...)
	if type(func) ~= "function" then
		return false, "Callback expected; got %s", type(func)
	end
	ab:SetAttribute("type-" .. id, "callCustom")
	abCallFuncs[tostring(id)] = cnt > 1 and {func, cnt+1, ...} or func
	return true
end

local handlers, describers, allocatedActions, nextActionId = {}, {}, {}, 42
local optData, optStart, optEnd = {}, {}, {}
function api:get(ident, ...)
	assert(type(ident) == "string", "Syntax: actionId = AB:get(identifier, ...)")
	if handlers[ident] then
		local id = handlers[ident](...)
		if allocatedActions[id] then
			return id
		end
	end
end
function api:info(id, ...)
	assert(type(id) == "number", "Syntax: usable, state, icon, caption, count, cdLeft, cdLength, tipFunc, tipArg = AB:info(actionId[, altState, shiftState, ctrlState])")
	if allocatedActions[id] then
		return allocatedActions[id](id, ...)
	end
end
function api:describe(ident, ...)
	assert(type(ident) == "string", "Syntax: typeName, actionName, icon = AB:describe(identifier, ...)")
	if describers[ident] then
		return describers[ident](...)
	end
end
function api:button(id)
	assert(type(id) == "number", "Syntax: buttonName, clickButton = AB:button(actionId)")
	return ab:GetName(), id
end
function api:options(ident)
	assert(type(ident) == "string", "Syntax: ... = AB:options(identifier)")
	return unpack(optData, optStart[ident] or 0, optEnd[ident] or -1)
end

function api:register(ident, create, describe, opt)
	assert(type(ident) == "string" and type(create) == "function" and type(describe) == "function" and (opt == nil or type(opt) == "table"), "Syntax: AB:register(identifier, createFunc, describeFunc[, {options}])")
	assert(not handlers[ident], "Identifier %q is already registered", atype)
	handlers[ident], describers[ident] = create, describe
	if opt and #opt > 0 then
		local ok
		for i=0,#optData-1 do ok = i for j=1,#opt do if opt[j] ~= optData[i+j] then ok = nil break end end if ok then break end end
		if not ok then ok = #optData for i=1,#opt do optData[ok+i] = opt[i] end end
		optStart[ident], optEnd[ident] = ok+1, ok + #opt
	end
end
function api:create(atype, infoFunc, ...)
	assert(type(atype) == "string" and type(infoFunc) == "function", "Syntax: actionId = AB:create(actionType, infoFunc, ...)")
	assert(actionTypeHandler[atype], "Action type %q is invalid", atype)
	local id = nextActionId
	nextActionId = nextActionId + 1
	assert(actionTypeHandler[atype](id, select("#", ...), ...))
	allocatedActions[id] = infoFunc
	return id
end

local notifyCount, observers = 1, {}
function api:notify(ident)
	assert(type(ident) == "string", "Syntax: AB:notify(identifier)");
	assert(ident == "*" or handlers[ident], "Identifier %q is not registered", ident);
	notifyCount = (notifyCount + 1) % 4503599627370495
	local erf = geterrorhandler()
	for k,v in pairs(observers) do
		xpcall(k, erf)
	end
end
function api:observe(ident, callback)
	assert(type(ident) == "string" and type(callback) == "function", "Syntax: AB:observe(identifier, callbackFunc)")
	assert(ident == "*" or handlers[ident], "Identifier %q is not registered", ident)
	observers[callback] = true
end
function api:lastupdate(ident)
	assert(type(ident) == "string", "Syntax: token = AB:lastupdate(identifier)");
	assert(ident == "*" or handlers[ident], "Identifier %q is not registered", ident);
	return notifyCount;
end

function api:compatible(maj, rev)
	return maj == MAJ and rev <= REV
end

OneRingLib.ext.ActionBook = api